package com.xiaoma.seckill.service.Impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoma.seckill.dao.SeckillDetailMapper;
import com.xiaoma.seckill.dao.SeckillMapper;
import com.xiaoma.seckill.entity.SeckillActivity;
import com.xiaoma.seckill.entity.ActivityGoodsRelation;
import com.xiaoma.seckill.entity.SeckillRedis;
import com.xiaoma.seckill.feign.GoodsFeignService;
import com.xiaoma.seckill.service.SeckillService;
import entity.Goods;
import entity.Payorder;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.ObjectInputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Author: xiaoma
 * @Date: 2021/7/26 19:51
 * @description:
 */
@Service
public class SeckillServiceImpl  extends ServiceImpl<SeckillMapper, SeckillActivity> implements SeckillService {
    @Autowired
    SeckillMapper seckillMapper;
    @Autowired
    SeckillDetailMapper seckillDetailMapper;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    GoodsFeignService goodsFeignService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    //redis中存储活动信息前缀
    private final String ACTIVITY_PREFIX="seckill:activity:";
    //redis中存储商品信息前缀
    private final String GOODS_PREFIX="seckill:goods";
    //信号量前缀
    private final String SEMAPHORE_PREFIX="seckill:stock:";
    /**
     * @Description  查询3天内秒杀商品进行库存预热
     * @Author Xiaoma
     * @Date 2021/8/20 18:13
     * @param
     * @return java.util.List<com.xiaoma.seckill.entity.SeckillActivity>
     **/
    @Override
    public List<SeckillActivity> getLate3DaySeckill() {
        //查询最近3天内的秒杀活动
        QueryWrapper<SeckillActivity> queryWrapper = new QueryWrapper<SeckillActivity>().between("start_time", getStartTime(), getEndTime());
        List<SeckillActivity> list = seckillMapper.selectList(queryWrapper);
        //给秒杀活动实体设置关联表属性
        List<SeckillActivity> res = list.stream().map((seckillActivity) -> {
            QueryWrapper<ActivityGoodsRelation> queryWrapper1 = new QueryWrapper<ActivityGoodsRelation>().eq("activity_id", seckillActivity.getId());
            seckillActivity.setActivityGoodsRelationList(seckillDetailMapper.selectList(queryWrapper1));
            return seckillActivity;
        }).collect(Collectors.toList());
        //缓存活动信息
        saveSeckill(res);
        //缓存商品信息
        saveGoods(res);
        return res;
    }
    /**
     * @Description  获取当前时间内的秒杀活动和商品信息
     * @Author Xiaoma
     * @Date 2021/8/20 20:54
     * @param
     * @return java.util.List<com.xiaoma.seckill.entity.SeckillRedis>
     **/
    @Override
    public List<SeckillRedis> getCurrentSeckill() {
        // 1.确定当前时间属于那个秒杀场次
        long time = new Date().getTime();
        //获取seckill:activity前缀的key
        Set<String> keys = redisTemplate.keys(ACTIVITY_PREFIX + "*");
        for (String key : keys) {
            // seckill:detail:1593993600000_1593995400000
            String replace = key.replace("seckill:activity:", "");
            String[] split = replace.split("_");
            long start = Long.parseLong(split[0]);
            long end = Long.parseLong(split[1]);
            if(time >= start && time <= end){
                // 2.获取这个秒杀场次的所有商品信息
                List<String> range = redisTemplate.opsForList().range(key, 0, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(GOODS_PREFIX);
                List<String> list = hashOps.multiGet(range);
                if(list != null){
                    return list.stream().map(item->{
                        SeckillRedis seckillRedis = JSON.parseObject(item, SeckillRedis.class);
                        return seckillRedis;
                    }).collect(Collectors.toList());
                }
                 break;
            }
        }
        return null;
    }
    /**
     * @Description  根据商品ID从缓存中获取商品信息
     * @Author Xiaoma
     * @Date 2021/8/20 20:57
     * @param id
     * @return com.xiaoma.seckill.entity.SeckillRedis
     **/
    @Override
    public SeckillRedis getGoodsByGoodsId(Integer id) {
        BoundHashOperations<String,String, String> ops = redisTemplate.boundHashOps(GOODS_PREFIX);
        //获取所有商品的hash key
        Set<String> keys = ops.keys();
        if (!keys.isEmpty()){
            String regx = "\\d-"+id;
            for (String key : keys) {
                //1_1 1_2
                //通过正则表达式匹配 数字-当前id的商品
                //从redis中获取商品信息
                if (Pattern.matches(regx,key)){
                    SeckillRedis seckillRedis = JSON.parseObject(ops.get(key), SeckillRedis.class);
                    //当前商品参与秒杀活动
                    long now = new Date().getTime();
                    if (now>seckillRedis.getStartTime()&&now<seckillRedis.getEndTime()){
                        //当前活动在有效期，暴露商品随机码返回
                        return seckillRedis;
                    }
                    //当前商品不再秒杀有效期，则隐藏秒杀所需的商品随机码
                    seckillRedis.setRandomCode(null);
                    return seckillRedis;
                }
            }
        }
            return null;
    }
    /**
     * @Description  //TODO  秒杀方法流程？
     * @Author Xiaoma
     * @Date 2021/8/20 20:57
     * @param killId    3-1 3-2
     * @param key
     * @param num
     * @param uid
     * @return java.lang.String
     **/
    @Override
    public String kill(String killId, String key, Integer num,Integer uid) {
        BoundHashOperations<String,String, String> ops = redisTemplate.boundHashOps(GOODS_PREFIX);
        String json = ops.get(killId);
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        SeckillRedis seckillRedis = JSON.parseObject(json, SeckillRedis.class);
        // 校验时间合法性
        long time = new Date().getTime();
        if(time < seckillRedis.getStartTime() && time > seckillRedis.getEndTime()){
            return null;
        }
        //校验随机码
        String randomCode = seckillRedis.getRandomCode();
        if  (!key.equals(randomCode)) {
            return null;
        }
        //校验购买数量
        if(num > seckillRedis.getActivityGoodsRelation().getSeckillLimit()) {
            return null;
        }
        //占位 验证该用户是否购买过 自动过期  用户id_活动id_商品id
        String redisKey= uid+"_"+seckillRedis.getActivityGoodsRelation().getActivityId()+"_"+seckillRedis.getGoods().getId();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), seckillRedis.getEndTime() - seckillRedis.getStartTime(), TimeUnit.MILLISECONDS);
        if (!flag) {
            return null;
        }
        //信号量
        RSemaphore semaphore = redissonClient.getSemaphore(SEMAPHORE_PREFIX + randomCode);
        // acquire阻塞  tryAcquire非阻塞
        boolean acquire = semaphore.tryAcquire(num);
        if (!acquire) {
            return null;
        }
        //发送消息
        Payorder payorder = new Payorder();
        payorder.setUid(uid);
        payorder.setGid(seckillRedis.getGoods().getId());
        payorder.setOrderStatus(1);
        rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order", payorder);
        return IdWorker.getTimeId();
    }

    /**
     * @Description  在redis中保存活动信息
     * @Author Xiaoma
     * @Date 2021/8/20 17:30
     * @param list
     * @return void
     **/
    private void  saveSeckill(List<SeckillActivity> list){
        list.forEach((seckillActivity)->{
            long start = seckillActivity.getStartTime().getTime();
            long end = seckillActivity.getEndTime().getTime();
            //key:seckill:activity:活动开始时间_活动结束时间
            String key= ACTIVITY_PREFIX+start+"_"+end;
            //value:[活动1id-商品1id，活动1id-商品2id]
            List<String> goodsList = seckillActivity.getActivityGoodsRelationList().stream().map(activityGoodsRelation -> activityGoodsRelation.getActivityId()+"-"+ activityGoodsRelation.getGoodsId().toString()).collect(Collectors.toList());
            //防止重复存储
            if (!redisTemplate.hasKey(key)){
                //保存活动信息
                redisTemplate.opsForList().leftPushAll(key,goodsList);
            }
        });
    }
    /**
     * @Description  在redis中保存商品信息
     * @Author Xiaoma
     * @Date 2021/8/20 17:34
     * @param list
     * @return void
     **/
    private void  saveGoods(List<SeckillActivity> list){
        //准备hash操作
        BoundHashOperations<String, Object, Object> OPS = redisTemplate.boundHashOps(GOODS_PREFIX);
        list.forEach((seckillActivity -> {
            seckillActivity.getActivityGoodsRelationList().forEach((activityGoodsRelation)->{
                Long goodsId = activityGoodsRelation.getGoodsId();
                //调用远程Goods服务 查询具体商品信息
                Goods good = goodsFeignService.queryGoodsById(Integer.parseInt(goodsId.toString()));
                //防止重复缓存
                if (!OPS.hasKey(seckillActivity.getId()+"-"+good.getId().toString())){
                    //缓存商品
                    SeckillRedis seckillRedis = new SeckillRedis();
                    //秒杀信息
                    seckillRedis.setActivityGoodsRelation(activityGoodsRelation);
                    //商品信息
                    seckillRedis.setGoods(good);
                    //秒杀开始结束时间
                    seckillRedis.setStartTime(seckillActivity.getStartTime().getTime());
                    seckillRedis.setEndTime(seckillActivity.getEndTime().getTime());
                    //随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    seckillRedis.setRandomCode(token);
                    //秒杀总量作为信号量
                    RSemaphore semaphore = redissonClient.getSemaphore(SEMAPHORE_PREFIX + token);
                    semaphore.trySetPermits(activityGoodsRelation.getSeckillCount());
                    //加入缓存
                    String json = JSON.toJSONString(seckillRedis);
                    OPS.put(seckillActivity.getId()+"-"+good.getId().toString(),json);
                }
            });
        }));
//        goods.forEach(System.out::println);
    }

    /**
     * @Description  当前天数的 00:00:00
     * @Author Xiaoma
     * @Date 2021/8/20 18:14
     * @param
     * @return java.lang.String
     **/
    private String getStartTime() {
        LocalDate now = LocalDate.now();
        LocalDateTime time = now.atTime(LocalTime.MIN);
        String format = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        return format;
    }

    /**
     * @Description  当前天数+2 23:59:59..
     * @Author Xiaoma
     * @Date 2021/8/20 18:14
     * @param
     * @return java.lang.String
     **/
    private String getEndTime() {
        LocalDate now = LocalDate.now();
        LocalDateTime time = now.plusDays(2).atTime(LocalTime.MAX);
        return time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}
